#include "lv_draw_img_jpeg_decoder.h"

#if USE_LV_DRAW_IMG_JPEG_DECODER

#include <stdio.h>
#include <stdbool.h>
#include <setjmp.h>
#include "lvgl/lvgl.h"
#include "lvgl/src/lv_misc/lv_mem.h"
#include "lvgl/src/lv_draw/lv_draw_img.h"
#include "lv_drivers/display/scaler.h"

#if USE_LV_DRAW_IMG_JPEG_DECODER_HW_MODE
#include "../../board.h"
#include "lv_drivers/jpeg/crane_jpuc_decoder.h"
#else
#if USE_LV_DRAW_IMG_JPEG_DECODER_INC_JLIB
#include "../external/libjpeg-turbo/jpeglib.h"
#else
#include "../../board.h"
#endif
#endif

#include "lv_drv_conf.h"
#include "lv_drivers/display/scaler.h"

#define LOGE(fmt, ...)   printf(fmt, ##__VA_ARGS__);
#define LOGW(fmt, ...)   printf(fmt, ##__VA_ARGS__);
#define LOGI(fmt, ...)   printf(fmt, ##__VA_ARGS__);
#define LOGD(fmt, ...)   //printf(fmt, ##__VA_ARGS__);

#define JPEG_CACHE_FOR_LARGE_SIZE   2
#define JPEG_CACHE_FOR_SMALL_SIZE   30
#define JPEG_CACHE_SIZE (JPEG_CACHE_FOR_LARGE_SIZE + JPEG_CACHE_FOR_SMALL_SIZE)
#define JPEG_LARGE_SIZE (200+200)  /* if width+height > 400, it's a large jpeg */

typedef struct {
    lv_fs_file_t * f; //at the end of open(), it will be closed, set NULL.
    uint8_t * data_buf;
#if !USE_LV_DRAW_IMG_JPEG_DECODER_HW_MODE
    struct jpeg_decompress_struct * jpeg_info;
#endif
} lv_jpeg_decoder_data_t;

void * jpeg_malloc(uint32_t size, uint16_t align)
{
    if(align == 0)
        align = 4;

    void * p = lv_mem_aligned_alloc(size, align);
    if(p == NULL) {
        LOGE("Error:  jpeg_malloc failed!! \n");
        return NULL;
    }
    memset(p, 0, size);
    return p;
}

void jpeg_free(void * p)
{
    if(p == NULL) {
        return;
    }

    lv_mem_aligned_free(p);
    return;
}

/*Need jpeg_free(buf) after buffer is no used;*/
static uint8_t * jpeg_read_file(const char * path, uint32_t * size)
{
    lv_fs_file_t file;
    uint32_t file_size = *size;

    lv_fs_res_t res = lv_fs_open(&file, path, LV_FS_MODE_RD);
    if(res == LV_FS_RES_OK) {
        if(file_size == 0) {
            if(LV_FS_RES_OK != lv_fs_size(&file, &file_size)) {
                printf("[%s]: lv_fs_size failed\n", __func__);
                goto err;
            }
        }

        if(file_size <= 0) {
            printf("[%s]: lv_fs_size get size=%d wrong\n", __func__, file_size);
            goto err;
        }

        uint32_t br = 0;
        uint8_t * buf = jpeg_malloc(file_size, 32);
        if(buf == NULL) {
            printf("[%s] No memory !!!\n", __func__);
            goto err;
        }
        memset(buf, 0, file_size);
        res = lv_fs_read(&file, buf, file_size, &br);
        *size = file_size;
        lv_fs_close(&file);
        return buf;
    } else {
        printf("[%s] error,path = %s,errno = %d !!!\n", __func__, path, res);
        return NULL;
    }

err:
    lv_fs_close(&file);
    return NULL;
}

#if 0
static bool jpeg_check_sig(const char * fn)
{
    uint32_t file_size = 5;
    uint8_t * buf = jpeg_read_file((const char *)fn, &file_size);
    if(buf == NULL) {
        return false;
    }

    if((buf[0] == 0xff) && (buf[1] == 0xd8)) {
        jpeg_free(buf);
        return true;
    }

    jpeg_free(buf);
    return false;
}
#endif

static bool jpeg_decoder_check_type(const void * src)
{
    const lv_img_dsc_t * img_src = (const lv_img_dsc_t *)src;

    /* img_src->data is file path */
    if(img_src->header.reserved == 1) {
        /* jpeg in romfs or psram need has ext as ".jpg", so no need open file to verify signature here.
         * Suggest the file received from voice message saved in nvm add ext ".jpg" by code also.
         */
        const char * fn = (const char *)img_src->data;
        if(!strcmp(lv_fs_get_ext(fn), "jpg")) {
#if 0
            if(!jpeg_check_sig(fn)) return false;
#endif
            return true;
        }
    } else {
        if ((img_src->data_size) >= 2) {
            if((img_src->data[0] == 0xff) && (img_src->data[1] == 0xd8)) {
                return true;
            }
        }
    }
    return false;
}

typedef struct jpeg_cache_info_s {
    const void * img_src;
    void * img_buf;
    uint16_t width;
    uint16_t height;
} jpeg_cache_info_t;

static jpeg_cache_info_t jpeg_cache[JPEG_CACHE_SIZE];

void jpeg_decoder_close_img_buf(const void * src)
{
    jpeg_cache_info_t * cache = jpeg_cache;
    int i;
    for(i = 0; i < JPEG_CACHE_SIZE; i++) {
        if(src == cache[i].img_src) {
            jpeg_free(cache[i].img_buf);
            cache[i].img_src = NULL;
            break;
        }
    }
}

void jpeg_decoder_close_all_img_buf(void)
{
    jpeg_cache_info_t * cache = jpeg_cache;
    int i;
    for(i = 0; i < JPEG_CACHE_SIZE; i++) {
        if(cache[i].img_src) {
            jpeg_free(cache[i].img_buf);
            cache[i].img_src = NULL;
        }
    }
}

__attribute__((unused)) static void jpeg_set_to_cache(const void * src, void * buf, jpeg_cache_info_t * cache, uint16_t width, uint16_t height)
{
    int i;
    static int update_id_large = 0;
    static int update_id_small = JPEG_CACHE_FOR_LARGE_SIZE;
    if((width + height) >= JPEG_LARGE_SIZE) {
        for(i = 0; i < JPEG_CACHE_FOR_LARGE_SIZE; i++) {
            if(cache[i].img_src == NULL) {
                break;
            }
        }

        if(i == JPEG_CACHE_FOR_LARGE_SIZE) {
            i = update_id_large;
            jpeg_free(cache[i].img_buf);
            update_id_large = (update_id_large + 1) % JPEG_CACHE_FOR_LARGE_SIZE;
        }
    } else {
        for(i = JPEG_CACHE_FOR_LARGE_SIZE; i < JPEG_CACHE_SIZE; i++) {
            if(cache[i].img_src == NULL) {
                break;
            }
        }

        if(i == JPEG_CACHE_SIZE) {
            i = update_id_small;
            jpeg_free(cache[i].img_buf);
            update_id_small = (update_id_small + 1) % JPEG_CACHE_SIZE;
            if(update_id_small == 0)
                update_id_small = JPEG_CACHE_FOR_LARGE_SIZE;
        }
    }

    cache[i].img_src = src;
    cache[i].img_buf = buf;
    cache[i].width = width;
    cache[i].height = height;
    return;
}

extern void invalidate_dcache_range(unsigned long start, unsigned long stop);

#if USE_LV_DRAW_IMG_JPEG_DECODER_HW_MODE
/**
 * Open a JPEG image and return the decided image
 * @param src can be file name or pointer to a C array
 * @param style style of the image object (unused now but certain formats might use it)
 * @return pointer to the decoded image or  `LV_RES_INV` if failed
 */
static lv_res_t jpeg_decoder_open(lv_img_decoder_t * decoder, lv_img_decoder_dsc_t * dsc)
{
    //uint32_t start_time = lv_tick_get();
    lv_fs_file_t f;
    const char * fn = NULL;
    lv_fs_res_t ret;
    uint32_t data_size;
    uint8_t * out_buffer_p = NULL;
    lv_jpeg_decoder_data_t * user_data = NULL;
    uint8_t *out_buffer_yuv = NULL;

    /*allocat user_data*/
    if(dsc->user_data == NULL) {
        dsc->user_data = lv_mem_alloc(sizeof(lv_jpeg_decoder_data_t));
        if(!dsc->user_data) {
            printf("[%s]: out of memory", __func__);
            goto err;
        }
        memset(dsc->user_data, 0, sizeof(lv_jpeg_decoder_data_t));
    }
    user_data = dsc->user_data;

    /*If it's a JPG file...*/
    /*A JPG file, not support Zoom in, show original size*/
    /*
     *eg:
     *"C:/xxx.jpg", means file in PSRAM;
     *"R:/xxx.jpg", means file in ROMFS XIP NOR Flash;
     *"P:/xxx.jpg", means file in PC;
     */
    if(dsc->src_type == LV_IMG_SRC_FILE) {
        fn = (char *)dsc->src;

        /*Support only "*.jpg" files*/
        if(strcmp(lv_fs_get_ext(fn), "jpg")) {
           printf("[%s]: file %s is not .jpg\n", __func__, fn);
           goto err;
        }

        ret = lv_fs_open(&f, fn, LV_FS_MODE_RD);
        if(ret != LV_FS_RES_OK) {
            printf("[%s]: lv_fs_open failed\n", __func__);
            goto err;
        }
        user_data->f = lv_mem_alloc(sizeof(f));
        if(!user_data->f) {
            printf("[%s]: out of memory", __func__);
            goto err;
        }
        memcpy(user_data->f, &f, sizeof(f));
    } else if(dsc->src_type == LV_IMG_SRC_VARIABLE) {
        lv_img_dsc_t * img = (lv_img_dsc_t *)dsc->src;
        if(img->header.cf != LV_IMG_CF_RAW) {
            printf("[%s] img->header.cf=%d\n", __func__, img->header.cf);
            goto err;
        }

        /* img->data is file path, support jpg zoom in */
        if(dsc->header.reserved == 1) {
            fn = (const char *)img->data;

            /*Support only "*.jpg" files*/
            if(strcmp(lv_fs_get_ext(fn), "jpg"))
            {
                printf("[%s]: file %s is not .jpg\n", __func__, fn);
                goto err;
            }

            ret = lv_fs_open(&f, fn, LV_FS_MODE_RD);
            if(ret != LV_FS_RES_OK) {
                printf("[%s]: lv_fs_open failed\n", __func__);
                goto err;
            }
            user_data->f = lv_mem_alloc(sizeof(f));
            if(!user_data->f) {
                printf("[%s]: out of memory", __func__);
                goto err;
            }
            memcpy(user_data->f, &f, sizeof(f));
        }
    } else {
        printf("[%s]: not support src_type\n", __func__);
        goto err;
    }

    //from file
    if(user_data->f) {
        ret = lv_fs_size(user_data->f, &data_size);
        if(ret != LV_FS_RES_OK) {
            printf("[%s]: lv_fs_size failed\n", __func__);
            goto err;
        }

        uint32_t br = 0;
        user_data->data_buf = jpeg_malloc(data_size, 32);
        if(!user_data->data_buf) {
            printf("[%s] No memory !!!\n", __func__);
            goto err;
        }
        memset(user_data->data_buf, 0, data_size);

        ret = lv_fs_read(user_data->f, user_data->data_buf, data_size, &br);
        if(ret != LV_FS_RES_OK || data_size != br) {
            printf("[%s]: lv_fs_open failed\n", __func__);
            goto err;
        }
    } else {
        lv_img_dsc_t * img = (lv_img_dsc_t *)dsc->src;
        user_data->data_buf = (uint8_t *)img->data;
        data_size = img->data_size;
    }

    //check height and width
    jpeg_header_info_t jpeg_header;

    if(jpeg_header_parser(user_data->data_buf, data_size, &jpeg_header) != CRANE_JPEG_DECODE_PASS){
        LOGE("[%s]: this file is error!!\n", __func__);
        goto err;
    }

    if(((jpeg_header.width != dsc->header.w) || (jpeg_header.height != dsc->header.h))
        && (dsc->header.w % 4)) {
        LOGE("[%s]: jpeg show width is not multiple of 4, failed!!\n", __func__);
        goto err;
    }

    flush_cache((unsigned long)user_data->data_buf, (unsigned long)data_size);

    out_buffer_yuv = (uint8_t *)jpeg_malloc(jpeg_header.width * jpeg_header.height * 3 / 2, 32);
    if(out_buffer_yuv == NULL) {
        goto err;
    }

    out_buffer_p = (uint8_t *)jpeg_malloc(dsc->header.w * dsc->header.h * 2, 32);
    if(out_buffer_p == NULL) {
        goto err;
    }
    invalidate_dcache_range((unsigned long)out_buffer_yuv ,
                   (unsigned long)out_buffer_yuv + jpeg_header.width * jpeg_header.height * 3 / 2);
    invalidate_dcache_range((unsigned long)out_buffer_p ,
                   (unsigned long)out_buffer_p + dsc->header.w * dsc->header.h * 2);
    if(jpu_decode(user_data->data_buf, data_size, &jpeg_header,
              out_buffer_yuv, out_buffer_p, dsc->header.w, dsc->header.h) != CRANE_JPEG_DECODE_PASS) {
        goto err;
    }

    jpeg_free((void *)out_buffer_yuv);
    out_buffer_yuv = NULL;
    dsc->img_data = out_buffer_p;

    //close fd
    if(user_data->f) {
        jpeg_free((void *)user_data->data_buf);
        user_data->data_buf = NULL;
        lv_fs_close(user_data->f);
        lv_mem_free(user_data->f);
        user_data->f = NULL;
    }

err:
    if(out_buffer_p) {
        jpeg_free((void *)out_buffer_p);
        out_buffer_p = NULL;
    }

    if(out_buffer_yuv) {
        jpeg_free((void *)out_buffer_yuv);
        out_buffer_yuv = NULL;
    }

    if(dsc->user_data) {
        if(user_data->f) {
            jpeg_free(user_data->data_buf);
            user_data->data_buf = NULL;

            lv_fs_close(user_data->f);
            lv_mem_free(user_data->f);
        }

        lv_mem_free(dsc->user_data);
        dsc->user_data = NULL;
    }

    LOGE("jpeg hw decode failed!!\n");
    return LV_RES_INV;
}

static void jpeg_decoder_close(lv_img_decoder_t * decoder, lv_img_decoder_dsc_t * dsc)
{
    (void)decoder; /*Unused*/

    LOGD("[%s] +++\n", __func__);

    lv_jpeg_decoder_data_t * user_data = (lv_jpeg_decoder_data_t *)dsc->user_data;

    if(user_data) {
        if(user_data->f) {
            jpeg_free(user_data->data_buf);
            user_data->data_buf = NULL;
        }

        if(dsc->img_data != NULL) {
            jpeg_free((void *)dsc->img_data);
            dsc->img_data = NULL;
        }

        if(user_data->f) {
            lv_fs_close(user_data->f);
            lv_mem_free(user_data->f);
        }

        lv_mem_free(user_data);
        dsc->user_data = NULL;
    }
}

static lv_res_t jpeg_decoder_read_line(lv_img_decoder_t * decoder, lv_img_decoder_dsc_t * dsc, lv_coord_t x,
                                       lv_coord_t y, lv_coord_t len, uint8_t * buf)
{
    (void)decoder; /*Unused*/

    LOGD("[%s]: %d, %d, %d \n", __func__, x, y, len);

    if(dsc->header.cf != LV_IMG_CF_RAW) {
        printf("[%s]: not support header.cf\n", __func__);
        goto err;
    }

    if(!dsc->img_data) {
        printf("[%s]: failed for empty dsc->img_data\n", __func__);
        goto err;
    }


    if((x > dsc->header.w) || (y > dsc->header.h)) {
        LOGE("[%s]: jpeg read line param failed: %d, %d !!\n", __func__, x, y);
        goto err;
    }
    memcpy(buf, dsc->img_data + (y * dsc->header.w + x) * 2, len * 2);
    return LV_RES_OK;

err:
    return LV_RES_INV;
}

#else

//!USE_LV_DRAW_IMG_JPEG_DECODER_HW_MODE
//SW jpeg decoder, used only when pc_simulator

/*merged from crane_jpuc_decoder for get jpg height and widh when jpeg_decoder_info*/
#define CRANE_JPEG_DECODE_PASS   1
#define CRANE_JPEG_DECODE_FAIL   0

#define JPEG_QTABLE_NUM  4
#define JPEG_QTABLE_SIZE 64
#define JPEG_HUFF_TABLE_LENGTH_SIZE  16
#define JPEG_HUFF_TABLE_DC_VALUE_SIZE   12
#define JPEG_HUFF_TABLE_AC_VALUE_SIZE   162
#define JPEG_HUFF_TABLE_DC_SIZE   (JPEG_HUFF_TABLE_LENGTH_SIZE + JPEG_HUFF_TABLE_DC_VALUE_SIZE)
#define JPEG_HUFF_TABLE_AC_SIZE   (JPEG_HUFF_TABLE_LENGTH_SIZE + JPEG_HUFF_TABLE_AC_VALUE_SIZE)

#define JPEG_HEADER_MARKER_DQT    0xdb
#define JPEG_HEADER_MARKER_SOS    0xda
#define JPEG_HEADER_MARKER_DHT    0xc4
#define JPEG_HEADER_MARKER_SOF0   0xc0
#define JPEG_HEADER_DHT_DC_LUM    0x00
#define JPEG_HEADER_DHT_DC_CHROM  0x01
#define JPEG_HEADER_DHT_AC_LUM    0x10
#define JPEG_HEADER_DHT_AC_CHROM  0x11

typedef struct jpeg_header_info_s {
    uint16_t width;
    uint16_t height;
    uint32_t sos_end_position;
    uint8_t  qtable_got;
    uint8_t  qtable[JPEG_QTABLE_NUM][JPEG_QTABLE_SIZE];
    uint8_t huff_is_default;
    uint8_t huff_got;
    uint8_t huff_table_dc_lum[JPEG_HUFF_TABLE_DC_SIZE];
    uint8_t huff_table_dc_chrom[JPEG_HUFF_TABLE_DC_SIZE];
    uint8_t huff_table_ac_lum[JPEG_HUFF_TABLE_AC_SIZE];
    uint8_t huff_table_ac_chrom[JPEG_HUFF_TABLE_AC_SIZE];
    uint8_t huff_table_dc_lum_length;
    uint8_t huff_table_dc_chrom_length;
    uint8_t huff_table_ac_lum_length;
    uint8_t huff_table_ac_chrom_length;
} jpeg_header_info_t;

#define BIT_0 (1 << 0)
#define BIT_1 (1 << 1)
#define BIT_2 (1 << 2)
#define BIT_3 (1 << 3)

static uint8_t jpeg_header_parser(uint8_t * data, uint32_t data_length, jpeg_header_info_t * header_info)
{
    /*
    1. ptr     v1
    2. width   v1
    3. height  v1
    4. huff table  v1
    5. huff table index : ac / dc  x
    6. Qtable  0xFFDB  v1
    */
    if(data == NULL || header_info == NULL) {
        printf("Error :please check jpeg_header_parser param!!\n");
        return CRANE_JPEG_DECODE_FAIL;
    }

    memset(header_info, 0, sizeof(jpeg_header_info_t));

    uint32_t offset = 2;  //skip oxFFD8
    uint16_t marker_length;
    jpeg_header_info_t jpeg_header_info;
    memset(&jpeg_header_info, 0, sizeof(jpeg_header_info_t));

    while(offset < data_length - 1) {
        while(data[offset] != 0xff) {
            offset ++;
            if(offset >= (data_length - 1))
                return CRANE_JPEG_DECODE_FAIL;
        }

        uint8_t marker = data[++offset];
        if(offset >= (data_length - 1)) {
            return CRANE_JPEG_DECODE_FAIL;
        }
        marker_length = (data[offset + 1] << 8) + data[offset + 2];
        //printf("jpeg_header_parser: marker: %x, offset: %x, length: %d\n", marker, offset, marker_length);
        switch(marker) {
            case JPEG_HEADER_MARKER_SOS: {
                uint32_t sos_end = offset + marker_length + 1;
                if(sos_end < data_length) {
                    jpeg_header_info.sos_end_position = offset + marker_length + 1;
                    //printf("jpeg sos_end_position: 0x%x\n", jpeg_header_info.sos_end_position);
                    if(jpeg_header_info.width != 0
                            && jpeg_header_info.height != 0
                            && jpeg_header_info.qtable_got != 0) {
                        memcpy(header_info, &jpeg_header_info, sizeof(jpeg_header_info_t));
                        return CRANE_JPEG_DECODE_PASS;
                    } else {
                        return CRANE_JPEG_DECODE_FAIL;
                    }
                } else {
                    return CRANE_JPEG_DECODE_FAIL;
                }
            }

            case JPEG_HEADER_MARKER_SOF0:
                if((offset + marker_length) < data_length) {
                    jpeg_header_info.width = (data[offset + 6] << 8) + data[offset + 7];
                    jpeg_header_info.height = (data[offset + 4] << 8) + data[offset + 5];
                    //printf("jpeg size: 0x%x, 0x%x\n", jpeg_header_info.width, jpeg_header_info.height);
                } else {
                    return CRANE_JPEG_DECODE_FAIL;
                }
                break;

            case JPEG_HEADER_MARKER_DQT:
                if((offset + marker_length) < data_length) {
                    if(marker_length > 0x43) {
                        printf("Warning:  jpeg DQT size is 0x%x !!!!\n", marker_length);
//                        return CRANE_JPEG_DECODE_FAIL;
                    }
                    int q_index = data[offset + 3];
                    if((q_index & 0xf) >= 4) {
                        printf("Err: jpeg_header_parser: qindex[0x%x] is error!!!\n", q_index);
                        return CRANE_JPEG_DECODE_FAIL;
                    }
                    jpeg_header_info.qtable_got |= (1 << (q_index & 0xf));
                    //printf("jpeg q_index: %d, %d\n", (uint8_t)q_index, jpeg_header_info.qtable_got);
                    memcpy(&(jpeg_header_info.qtable[q_index & 0xf][0]), &(data[offset + 4]), marker_length - 3);
                } else {
                    return CRANE_JPEG_DECODE_FAIL;
                }
                break;

            case JPEG_HEADER_MARKER_DHT:
                if((offset + marker_length) < data_length) {
                    int huff_idx = data[offset + 3];
                    if(((huff_idx & 0xF0) && (marker_length > 0xB5))
                            || (((huff_idx & 0xF0) == 0) && (marker_length > 0x1F))) {
                        printf("Error:  jpeg HUFF[0x%x] size is 0x%x !!!!\n", huff_idx, marker_length);
//                        return CRANE_JPEG_DECODE_FAIL;
                    }
                    if(huff_idx == JPEG_HEADER_DHT_DC_LUM) {
                        memcpy(&(jpeg_header_info.huff_table_dc_lum[0]), &(data[offset + 4]), marker_length - 3);
                        jpeg_header_info.huff_got |= BIT_0;
                        jpeg_header_info.huff_table_dc_lum_length = marker_length - 3 - JPEG_HUFF_TABLE_LENGTH_SIZE;
                    } else if(huff_idx == JPEG_HEADER_DHT_DC_CHROM) {
                        memcpy(&(jpeg_header_info.huff_table_dc_chrom[0]), &(data[offset + 4]), marker_length - 3);
                        jpeg_header_info.huff_got |= BIT_1;
                        jpeg_header_info.huff_table_dc_chrom_length = marker_length - 3 - JPEG_HUFF_TABLE_LENGTH_SIZE;
                    } else if(huff_idx == JPEG_HEADER_DHT_AC_LUM) {
                        memcpy(&(jpeg_header_info.huff_table_ac_lum[0]), &(data[offset + 4]), marker_length - 3);
                        jpeg_header_info.huff_got |= BIT_2;
                        jpeg_header_info.huff_table_ac_lum_length = marker_length - 3 - JPEG_HUFF_TABLE_LENGTH_SIZE;
                    } else if(huff_idx == JPEG_HEADER_DHT_AC_CHROM) {
                        memcpy(&(jpeg_header_info.huff_table_ac_chrom[0]), &(data[offset + 4]), marker_length - 3);
                        jpeg_header_info.huff_got |= BIT_3;
                        jpeg_header_info.huff_table_ac_chrom_length = marker_length - 3 - JPEG_HUFF_TABLE_LENGTH_SIZE;
                    }
                } else {
                    return CRANE_JPEG_DECODE_FAIL;
                }
                break;

            default:
                break;
        }

        offset += marker_length;
        //printf("jpeg_header_parser: marker_length: %x \n", marker_length);
        offset++;
    }

    return CRANE_JPEG_DECODE_FAIL;
}
//end crane_jpuc_decoder

typedef struct  {
    struct jpeg_error_mgr pub; /* "public" fields */
    jmp_buf setjmp_buffer; /* for return to caller */
} lv_jpeg_error_manager_t;

static lv_jpeg_error_manager_t jerr;

/*jpeg_finish_decompress, can make exit, so jpeg_error_exit also will be invoked by normal finish, then go to err_jmp.*/
static void jpeg_error_exit(j_common_ptr cinfo)
{
    /* cinfo->err actually points to a jerr struct */
    lv_jpeg_error_manager_t * custom_err = (lv_jpeg_error_manager_t *) cinfo->err;
    //LOGE("[%s]jpeg decoder exit!!\n", __func__);
    (*cinfo->err->output_message)(cinfo);
    /* Jump to the setjmp point */
    longjmp(custom_err->setjmp_buffer, 1);
}

/**
 * Open a JPEG image and return the decided image
 * @param src can be file name or pointer to a C array
 * @param style style of the image object (unused now but certain formats might use it)
 * @return pointer to the decoded image or  `LV_RES_INV` if failed
 */
static lv_res_t jpeg_decoder_open(lv_img_decoder_t * decoder, lv_img_decoder_dsc_t * dsc)
{
    //uint32_t start_time = lv_tick_get();
    lv_fs_file_t f;
    const char * fn = NULL;
    lv_fs_res_t ret;
    uint32_t data_size;
    uint8_t * rgb888_buf = NULL;
    uint8_t * out_buffer_p = NULL;
    struct jpeg_decompress_struct * jpeg_info = NULL;
    lv_jpeg_decoder_data_t * user_data = NULL;
    lv_res_t res = LV_RES_INV;

    /*allocat user_data*/
    if(dsc->user_data == NULL) {
        dsc->user_data = lv_mem_alloc(sizeof(lv_jpeg_decoder_data_t));
        if(!dsc->user_data) {
            printf("[%s]: out of memory", __func__);
            return LV_RES_INV;
        }
        memset(dsc->user_data, 0, sizeof(lv_jpeg_decoder_data_t));
    }
    user_data = dsc->user_data;

    /*If it's a JPG file...*/
    /*A JPG file, not support Zoom in, show original size*/
    /*
     *eg:
     *"C:/xxx.jpg", means file in PSRAM;
     *"R:/xxx.jpg", means file in ROMFS XIP NOR Flash;
     *"P:/xxx.jpg", means file in PC;
     */
    if(dsc->src_type == LV_IMG_SRC_FILE) {
        fn = (char *)dsc->src;

        /*Support only "*.jpg" files*/
        if(strcmp(lv_fs_get_ext(fn), "jpg"))
        {
            printf("[%s]: file %s is not .jpg\n", __func__, fn);
            goto err;
        }

        ret = lv_fs_open(&f, fn, LV_FS_MODE_RD);
        if(ret != LV_FS_RES_OK) {
            printf("[%s]: lv_fs_open failed\n", __func__);
            goto err;
        }
        user_data->f = lv_mem_alloc(sizeof(f));
        if(!user_data->f) {
            printf("[%s]: out of memory", __func__);
            goto err;
        }
        memcpy(user_data->f, &f, sizeof(f));
    } else if(dsc->src_type == LV_IMG_SRC_VARIABLE) {
        lv_img_dsc_t * img = (lv_img_dsc_t *)dsc->src;
        if(img->header.cf != LV_IMG_CF_RAW) {
            printf("[%s] img->header.cf=%d\n", __func__, img->header.cf);
            goto err;
        }

        /* img->data is file path, support jpg zoom in */
        if(dsc->header.reserved == 1) {
            fn = (const char *)img->data;

            /*Support only "*.jpg" files*/
            if(strcmp(lv_fs_get_ext(fn), "jpg"))
            {
                printf("[%s]: file %s is not .jpg\n", __func__, fn);
                goto err;
            }

            ret = lv_fs_open(&f, fn, LV_FS_MODE_RD);
            if(ret != LV_FS_RES_OK) {
                printf("[%s]: lv_fs_open failed\n", __func__);
                goto err;
            }
            user_data->f = lv_mem_alloc(sizeof(f));
            if(!user_data->f) {
                printf("[%s]: out of memory", __func__);
                goto err;
            }
            memcpy(user_data->f, &f, sizeof(f));
        }
    } else {
        printf("[%s]: not support src_type\n", __func__);
        goto err;
    }

    //from file
    if(user_data->f) {
        ret = lv_fs_size(user_data->f, &data_size);
        if(ret != LV_FS_RES_OK) {
            printf("[%s]: lv_fs_size failed\n", __func__);
            goto err;
        }

        uint32_t br = 0;
        user_data->data_buf = jpeg_malloc(data_size, 32);
        if(!user_data->data_buf) {
            printf("[%s] No memory !!!\n", __func__);
            goto err;
        }
        memset(user_data->data_buf, 0, data_size);

        ret = lv_fs_read(user_data->f, user_data->data_buf, data_size, &br);
        if(ret != LV_FS_RES_OK || data_size != br) {
            printf("[%s]: lv_fs_open failed\n", __func__);
            goto err;
        }
    } else {
        lv_img_dsc_t * img = (lv_img_dsc_t *)dsc->src;
        user_data->data_buf = (uint8_t *)img->data;
        data_size = img->data_size;
    }

    if(!user_data->jpeg_info) {
        user_data->jpeg_info = lv_mem_alloc(sizeof(struct jpeg_decompress_struct));
        if(!user_data->jpeg_info) {
            printf("[%s]: out of memory", __func__);
            goto err;
        }
        memset(user_data->jpeg_info, 0, sizeof(struct jpeg_decompress_struct));
    }
    jpeg_info = user_data->jpeg_info;

    memset(&jerr, 0, sizeof(lv_jpeg_error_manager_t));
    jpeg_create_decompress(jpeg_info);
    jpeg_info->err = jpeg_std_error(&jerr.pub);
    jerr.pub.error_exit = jpeg_error_exit;
    /* Establish the setjmp return context for my_error_exit to use. */
    if(setjmp(jerr.setjmp_buffer)) {
        /* If we get here, the JPEG code has signaled an error. */
        printf("[%s]: JPEG code has signaled an error\n", __func__);
        goto err_jmp;//to finished
    }

    jpeg_mem_src(jpeg_info, user_data->data_buf, data_size);

    if(jpeg_read_header(jpeg_info, TRUE) == -1) {
        LOGE("[%s]: read header err !!\n", __func__);
        goto err;
    }

    int num_components = jpeg_info->num_components;
    if(JCS_GRAYSCALE == jpeg_info->out_color_space)
    {
        jpeg_info->out_color_space = JCS_RGB;
        num_components = 3;
    }

    uint32_t w = jpeg_info->image_width;
    uint32_t h = jpeg_info->image_height;
    uint32_t scale_denom = 1;
    while((w >= dsc->header.w * 2) && (h >= dsc->header.h * 2)) {
        w /= 2;
        h /= 2;
        scale_denom *= 2;
    }

    if(scale_denom > 1) {
        jpeg_info->scale_num = 1;
        jpeg_info->scale_denom = scale_denom > 8 ? 8 : scale_denom;
    }

    if((jpeg_start_decompress(jpeg_info) == false)) {
        LOGE("[%s]: decompress failed !!\n", __func__);
        goto err;
    }

    uint32_t out_buffer_size = jpeg_info->output_width * jpeg_info->output_height * num_components;
    rgb888_buf = jpeg_malloc(out_buffer_size, 0);
    if(!rgb888_buf) {
        LOGI("[%s]: no enough memory!!\n", __func__);
        goto err;
    }

    while(jpeg_info->output_scanline < jpeg_info->output_height) {
        JSAMPROW out_str  = (JSAMPROW)(rgb888_buf +
                                       jpeg_info->output_scanline * jpeg_info->output_width * num_components);
        if((jpeg_read_scanlines(jpeg_info, &out_str, 1) != 1)) {
            LOGE("lv_img_decoder_open_jpeg: scan line failed!!\n");
            goto err;
        }
    }

    //uint32_t mid = lv_tick_get();
    out_buffer_p = jpeg_malloc(dsc->header.w * dsc->header.h * 2, 32);
    if(!out_buffer_p) {
        LOGE("lv_img_decoder_open_jpeg: no enough memory\n");
        goto err;
    }

    if((jpeg_info->output_width == dsc->header.w) && (jpeg_info->output_height == dsc->header.h)) {
        lv_color_rgb888_to_16((lv_color_rgb888_t *)rgb888_buf,
                              (lv_color16_t *)out_buffer_p,
                              jpeg_info->output_width * jpeg_info->output_height);
    } else {
        if(rgb888_2_rgb565_scale(jpeg_info->output_width,
                                      jpeg_info->output_height,
                                      dsc->header.w,
                                      dsc->header.h,
                                      rgb888_buf,
                                      out_buffer_p) == 0) {
            goto err;
        }
    }

    //uint32_t end = lv_tick_get();
    //LOGI("lv_img_decoder_open_jpeg : addr : %p, size: %d [%d, %d] \n", out_buffer_p, out_buffer_size, mid-start_time, end-mid);
    dsc->img_data = out_buffer_p;

    res = LV_RES_OK;

err:
    if(user_data && user_data->jpeg_info && user_data->jpeg_info->global_state != 0) {
        //jpeg_finish_decompress, make error exit, so jpeg_error_exit will be invoked, then go to err_jmp.
        jpeg_finish_decompress(user_data->jpeg_info);
    }

err_jmp:
    if(rgb888_buf) {
        jpeg_free((void *)rgb888_buf);
        rgb888_buf = NULL;
    }

    if(out_buffer_p && (res != LV_RES_OK)) {
        jpeg_free((void *)out_buffer_p);
        out_buffer_p = NULL;
    }

    if(dsc->user_data) {
        if(user_data->f) {
            jpeg_free(user_data->data_buf);
            user_data->data_buf = NULL;

            lv_fs_close(user_data->f);
            lv_mem_free(user_data->f);
            user_data->f = NULL;
        }

        if(user_data->jpeg_info) {
            jpeg_destroy_decompress(user_data->jpeg_info);
            lv_mem_free(user_data->jpeg_info);
            user_data->jpeg_info = NULL;
        }

        lv_mem_free(dsc->user_data);
        dsc->user_data = NULL;
    }

    return res;
}

static void jpeg_decoder_close(lv_img_decoder_t * decoder, lv_img_decoder_dsc_t * dsc)
{
    (void)decoder; /*Unused*/

    LOGD("[%s] +++\n", __func__);
    if(dsc->img_data != NULL) {
        jpeg_free((void *)dsc->img_data);
        dsc->img_data = NULL;
    }
}

/**
 * Decode `len` pixels starting from the given `x`, `y` coordinates and store them in `buf`.
 * Required only if the "open" function can't return with the whole decoded pixel array.
 * @param decoder pointer to the decoder the function associated with
 * @param dsc pointer to decoder descriptor
 * @param x start x coordinate
 * @param y start y coordinate
 * @param len number of pixels to decode
 * @param buf a buffer to store the decoded pixels
 * @return LV_RES_OK: ok; LV_RES_INV: failed
 */
static lv_res_t jpeg_decoder_read_line(lv_img_decoder_t * decoder, lv_img_decoder_dsc_t * dsc, lv_coord_t x,
                                       lv_coord_t y, lv_coord_t len, uint8_t * buf)
{
#if 0
    (void)decoder; /*Unused*/

    LOGD("[%s]: %d, %d, %d \n", __func__, x, y, len);
    lv_jpeg_decoder_data_t * user_data = dsc->user_data;

    if(dsc->header.cf != LV_IMG_CF_RAW) {
        printf("[%s]: not support header.cf\n", __func__);
        goto err;
    }

    if(!user_data) {
        printf("[%s]: failed for empty user_data\n", __func__);
        goto err;
    }

    struct jpeg_decompress_struct * jpeg_info = user_data->jpeg_info;
    if(!jpeg_info) {
        printf("[%s]: failed for empty jpeg_info\n", __func__);
        goto err;
    }

    /* Establish the setjmp return context for my_error_exit to use. */
    if(setjmp(jerr.setjmp_buffer)) {
        /* If we get here, the JPEG code has signaled an error. */
        printf("[%s]: JPEG code has signaled an error\n", __func__);
        goto err_jmp;
    }

    if(jpeg_info->global_state == 0) {
        LOGE("[%s] error: Please open jpeg first!! \n", __func__);
        goto err;
    }

    if(y < jpeg_info->output_scanline) {
        LOGE("[%s] error: jpeg lib not support read from back to front!! \n", __func__);
        goto err;
    }

    if(!buf || x >= jpeg_info->image_width || y >= jpeg_info->image_height) {
        LOGE("[%s] error: Error: lv_img_decoder_read_line_jpeg param is error!! \n", __func__);
        goto err;
    }

    if(x != 0) {
        JDIMENSION width = len;
        JDIMENSION offset = x;
        jpeg_crop_scanline(jpeg_info, &offset, &width);
    }

    if(y > jpeg_info->output_scanline) {
        jpeg_skip_scanlines(jpeg_info, (y - jpeg_info->output_scanline));
    }

    if(jpeg_read_scanlines(jpeg_info, (JSAMPARRAY)buf, 1) != 1) {
        LOGE("[%s]: scan line failed!!\n", __func__);
        goto err;
    }

    lv_color_rgb888_to_16((lv_color_rgb888_t *)buf, (lv_color16_t *)buf, len);

    return LV_RES_OK;

err:
    if(user_data && user_data->jpeg_info && user_data->jpeg_info->global_state != 0) {
        //jpeg_finish_decompress, make error exit, so jpeg_error_exit will be invoked, then go to err_jmp.
        jpeg_finish_decompress(user_data->jpeg_info);
    }

err_jmp:
    if(dsc->user_data) {
        if(user_data->f) {
            jpeg_free(user_data->data_buf);
            user_data->data_buf = NULL;

            lv_fs_close(user_data->f);
            lv_mem_free(user_data->f);
        }

        if(user_data->jpeg_info) {
            jpeg_destroy_decompress(user_data->jpeg_info);
            lv_mem_free(user_data->jpeg_info);
        }

        lv_mem_free(dsc->user_data);
        dsc->user_data = NULL;
    }
#endif
    return LV_RES_INV;
}
#endif

/**
 * Get info about a JPEG image
 * @param src can be file name or pointer to a C array
 * @param header store the info here
 * @return LV_RES_OK: no error; LV_RES_INV: can't get the info
 */
static lv_res_t jpeg_decoder_info(struct _lv_img_decoder * decoder, const void * src, lv_img_header_t * header)
{
    (void) decoder; /*Unused*/
    lv_img_src_t src_type = lv_img_src_get_type(src);          /*Get the source type*/
    uint8_t * data_buf = NULL;

    /*If it's a JPEG file...*/
    /*A JPEG file, not support Zoom in, show original size*/
    /*
     *eg:
     *"C:/xxx.jpg", means file in PSRAM;
     *"R:/xxx.jpg", means file in ROMFS XIP NOR Flash;
     *"C:/xxx.jpg", means file in PC;
     */
    if(src_type == LV_IMG_SRC_FILE) {
        const char * fn = src;
        /*Support only "*.jpg" files*/
        if(strcmp(lv_fs_get_ext(fn), "jpg")) return LV_RES_INV;

        uint32_t data_size = 0;
        data_buf = jpeg_read_file(fn, &data_size);
        if(data_buf == NULL) {
            goto err;
        }

        jpeg_header_info_t jpeg_header;
        if(jpeg_header_parser(data_buf, data_size, &jpeg_header) != CRANE_JPEG_DECODE_PASS) {
            LOGE("[%s]: this file is error!!\n", __func__);
            goto err;
        }

        if(jpeg_header.width % 4) {
            LOGE("[%s]: jpeg show width is not multiple of 4, failed!!\n", __func__);
            jpeg_free(data_buf);
            data_buf = NULL;
            goto err;
        }

        jpeg_free(data_buf);
        data_buf = NULL;

        /*Save the data in the header*/
        header->always_zero = 0;
        header->cf = LV_IMG_CF_RAW;
        header->reserved = 1; //if reserved = 1. means file
        header->w = jpeg_header.width;
        header->h = jpeg_header.height;
        //original w and h from file need analyse when open.

        jpeg_free(data_buf);
        return LV_RES_OK;
    }
    /*If it's a JPG file in a  C array...*/
    /* C array supports JPG zoom in:
     * the w and h from header of src is the target show size,
     * the w and h from info read from JPG binary is original size,
     * src->data can be JPG binary(header->reserved = 0) or JPG file(header->reserved = 1, in PSRAM or ROMFS),
     * header->cf = LV_IMG_CF_RAW;
     */
    else if(src_type == LV_IMG_SRC_VARIABLE) {
        if(!jpeg_decoder_check_type(src)) return LV_RES_INV;
        const lv_img_dsc_t * img_dsc = src;
        header->always_zero = 0;
        header->cf = img_dsc->header.cf;       /*Save the color format*/
        header->reserved = img_dsc->header.reserved;
        header->w = img_dsc->header.w;         /*Save the color width*/
        header->h = img_dsc->header.h;         /*Save the color height*/
        return LV_RES_OK;
    }

err:
    if(data_buf) jpeg_free(data_buf);
    return LV_RES_INV;
}

/**
 * Register the JPEG decoder functions in LittlevGL
 */
void lv_jpeg_decoder_init(void)
{
    lv_img_decoder_t * dec = lv_img_decoder_create();
    lv_img_decoder_set_info_cb(dec, jpeg_decoder_info);
    lv_img_decoder_set_open_cb(dec, jpeg_decoder_open);
    lv_img_decoder_set_read_line_cb(dec, jpeg_decoder_read_line);
    lv_img_decoder_set_close_cb(dec, jpeg_decoder_close);
}

#ifndef USE_WATCH_LITE
/**
 * compress and scale true color image in rgb565 format to jpeg.
 * srcBuf: rgb565 data buffer. e.g. image from camera preview
 * path: jpeg file path
 * input and output width/height should be multiple of 4
 */
void lv_jpeg_compress(const char * srcBuf, char * path, int input_width, int input_height, int output_width, int output_height)
{
    struct jpeg_compress_struct cinfo;
    struct jpeg_error_mgr jerr_mgr;
    unsigned long outSize = 0;
    int encQuality = 75;
    unsigned char *outData = NULL;
    unsigned int i;
    lv_fs_file_t file;
    uint32_t rn;
    JSAMPROW *row_pointer = NULL;
    lv_color_t * img_565_buf = (lv_color_t *)srcBuf;
    lv_color_rgb888_t * img_rgb_buf = NULL;
    bool scaled = false;

    //scale image
    if(input_width != output_width || input_height != output_height) {
        scaled = true;
        img_565_buf = lv_mem_alloc(output_width * output_height * (LV_COLOR_SIZE / 8));
        LV_ASSERT_MEM(img_565_buf);
        rgb565_scale(input_width, input_height, output_width, output_height, (uint8_t *)srcBuf, (uint8_t *)img_565_buf);
    }

    //get rgb buffer
    img_rgb_buf = (lv_color_rgb888_t *)lv_mem_alloc(output_width * output_height * sizeof(lv_color_rgb888_t));
    LV_ASSERT_MEM(img_rgb_buf);
    for(i = 0; i < output_width * output_height; i++) {
        img_rgb_buf[i].r = LV_COLOR_GET_R(img_565_buf[i]) << 3;
        img_rgb_buf[i].g = LV_COLOR_GET_G(img_565_buf[i]) << 2;
        img_rgb_buf[i].b = LV_COLOR_GET_B(img_565_buf[i]) << 3;
    }

    if(scaled) lv_mem_free(img_565_buf);

    jerr_mgr.error_exit = jpeg_error_exit;
    cinfo.err = jpeg_std_error(&jerr_mgr);

    jpeg_create_compress(&cinfo);
    jpeg_mem_dest(&cinfo, &outData, &outSize);

    cinfo.image_width = output_width; /* image width and height, in pixels */
    cinfo.image_height = output_height;
    cinfo.input_components = 3; /* # of color components per pixel */
    cinfo.in_color_space = JCS_RGB; /* colorspace of input image */
    jpeg_set_defaults(&cinfo);
    jpeg_set_quality(&cinfo, encQuality, TRUE);
    jpeg_start_compress(&cinfo, TRUE);

    int pitch = output_width * 3; /* 3 bytes in rgb888 format */
    if ((row_pointer = (JSAMPROW *)lv_mem_alloc(sizeof(JSAMPROW) * output_height)) == NULL) {
        printf("[%s] row_pointer  malloc failed\n", __func__);
        lv_mem_free(img_rgb_buf);
        return;
    }
    for (i = 0; i < output_height; i++) {
        row_pointer[i] = (JSAMPROW)&((char *)img_rgb_buf)[i * pitch];
    }

    jpeg_write_scanlines(&cinfo, &row_pointer[0], output_height);
    jpeg_finish_compress(&cinfo);
    jpeg_destroy_compress(&cinfo);
    lv_mem_free(img_rgb_buf);

    lv_fs_res_t res = lv_fs_open(&file, path, LV_FS_MODE_WR);
    if(res == LV_FS_RES_OK) {
        res = lv_fs_write(&file, outData, outSize, &rn);
        if(res != LV_FS_RES_OK) {
            printf("[%s] write copressed file failed\n", __func__);
            lv_fs_close(&file);
            if (row_pointer) lv_mem_free(row_pointer);
            return;
        }
        lv_fs_close(&file);
    }

    if (row_pointer) lv_mem_free(row_pointer);
}
#endif
#endif /* USE_LV_DRAW_IMG_JPEG_DECODER */
